19. Switch to Using a Loader
Switch to Using a Loader
Question:
Start Quiz:
Solution:
To define the EarthquakeLoader class, we extend AsyncTaskLoader and specify List
In EarthquakeLoader.java:
package com.example.android.quakereport;
import android.content.AsyncTaskLoader;
import android.content.Context;
import java.util.List;
/**
* Loads a list of earthquakes by using an AsyncTask to perform the
* network request to the given URL.
*/
public class EarthquakeLoader extends AsyncTaskLoader<List<Earthquake>> {
/** Tag for log messages */
private static final String LOG_TAG = EarthquakeLoader.class.getName();
/** Query URL */
private String mUrl;
/**
* Constructs a new {@link EarthquakeLoader}.
*
* @param context of the activity
* @param url to load data from
*/
public EarthquakeLoader(Context context, String url) {
super(context);
mUrl = url;
}
@Override
protected void onStartLoading() {
forceLoad();
}
/**
* This is on a background thread.
*/
@Override
public List<Earthquake> loadInBackground() {
if (mUrl == null) {
return null;
}
// Perform the network request, parse the response, and extract a list of earthquakes.
List<Earthquake> earthquakes = QueryUtils.fetchEarthquakeData(mUrl);
return earthquakes;
}
}
Implementing the LoaderCallbacks in our activity is a little more complex. First we need to say that EarthquakeActivity implements the LoaderCallbacks interface, along with a generic parameter specifying what the loader will return (in this case an Earthquake).
In EarthquakeActivity.java:
public class EarthquakeActivity extends AppCompatActivity implements LoaderCallbacks<List<Earthquake>> {
By the way, remember to import the following statements so that we can reference these classes in the code.
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
First we need to specify an ID for our loader. This is only really relevant if we were using multiple loaders in the same activity. We can choose any integer number, so we choose the number 1.
/**
* Constant value for the earthquake loader ID. We can choose any integer.
* This really only comes into play if you're using multiple loaders.
*/
private static final int EARTHQUAKE_LOADER_ID = 1;
Then we need to override the three methods specified in the LoaderCallbacks interface. We need onCreateLoader(), for when the LoaderManager has determined that the loader with our specified ID isn't running, so we should create a new one.
@Override
public Loader<List<Earthquake>> onCreateLoader(int i, Bundle bundle) {
// Create a new loader for the given URL
return new EarthquakeLoader(this, USGS_REQUEST_URL);
}
We need onLoadFinished(), where we'll do exactly what we did in onPostExecute(), and use the earthquake data to update our UI - by updating the dataset in the adapter.
@Override
public void onLoadFinished(Loader<List<Earthquake>> loader, List<Earthquake> earthquakes) {
// Clear the adapter of previous earthquake data
mAdapter.clear();
// If there is a valid list of {@link Earthquake}s, then add them to the adapter's
// data set. This will trigger the ListView to update.
if (earthquakes != null && !earthquakes.isEmpty()) {
mAdapter.addAll(earthquakes);
}
}
And we need onLoaderReset(), we're we're being informed that the data from our loader is no longer valid. This isn't actually a case that's going to come up with our simple loader, but the correct thing to do is to remove all the earthquake data from our UI by clearing out the adapter’s data set.
@Override
public void onLoaderReset(Loader<List<Earthquake>> loader) {
// Loader reset, so we can clear out our existing data.
mAdapter.clear();
}
Finally, to retrieve an earthquake, we need to get the loader manager and tell the loader manager to initialize the loader with the specified ID, the second argument allows us to pass a bundle of additional information, which we'll skip. The third argument is what object should receive the LoaderCallbacks (and therefore, the data when the load is complete!) - which will be this activity. This code goes inside the onCreate() method of the EarthquakeActivity, so that the loader can be initialized as soon as the app opens.
@Override
protected void onCreate(Bundle savedInstanceState) {
…
// Get a reference to the LoaderManager, in order to interact with loaders.
LoaderManager loaderManager = getLoaderManager();
// Initialize the loader. Pass in the int ID constant defined above and pass in null for
// the bundle. Pass in this activity for the LoaderCallbacks parameter (which is valid
// because this activity implements the LoaderCallbacks interface).
loaderManager.initLoader(EARTHQUAKE_LOADER_ID, null, this);
}
It’s now safe to delete the EarthquakeAsyncTask class that was declared within the EarthquakeActivity. You should have also removed the code to create and execute the task within the activity’s onCreate() method. Delete the AsyncTask import statement at the top of the file too.
When you run the app on your device, there should be no errors, and you should see the same list of earthquakes as before - except your code underneath is a lot more robust!
To see the full code difference, take a look at this GitHub code checkpoint.
INSTRUCTOR NOTE:
In case you’re wondering, see this definition of code refactoring.
Hints:
Code gist with a skeleton of the solution code.
Remember to import the right Loader class. It should be
import android.content.AsyncTaskLoader;
instead ofimport android.support.v4.content.AsyncTaskLoader;
.